package cn.lingyangwl.agile.system.service.impl;

import cn.hutool.core.util.ObjUtil;
import cn.lingyangwl.agile.auth.AuthApi;
import cn.lingyangwl.agile.common.security.utils.SecurityUtils;
import cn.lingyangwl.agile.model.constants.TenantCons;
import cn.lingyangwl.agile.model.constants.TreeCons;
import cn.lingyangwl.agile.model.enums.AuthErrorEnum;
import cn.lingyangwl.agile.model.enums.BoolEnum;
import cn.lingyangwl.agile.model.module.auth.AuthUserTenant;
import cn.lingyangwl.agile.model.module.auth.LoginUser;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.agile.system.assembly.SysMenuAssembly;
import cn.lingyangwl.agile.system.constants.CacheCons;
import cn.lingyangwl.agile.system.constants.MenuCons;
import cn.lingyangwl.agile.system.manager.*;
import cn.lingyangwl.agile.system.mapper.SysMenuMapper;
import cn.lingyangwl.agile.system.mapper.SysRoleMapper;
import cn.lingyangwl.agile.system.model.entity.*;
import cn.lingyangwl.agile.system.model.rqrs.menu.*;
import cn.lingyangwl.agile.system.service.SysMenuService;
import cn.lingyangwl.framework.core.utils.spring.SpringUtils;
import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.exception.Assert;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import cn.lingyangwl.framework.tool.core.tree.TreeUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu>
        implements SysMenuService {
    @Resource
    private SysMenuMapper menuDataMapper;
    @Resource
    private SysRoleMenuRelManager roleMenuRelManager;
    @Resource
    private SysRoleUserManager roleUserRelManager;
    @Resource
    private SysMenuManager menuManager;
    @Resource
    private SysRoleMapper roleMapper;
    @Resource
    private SysMenuAssembly menuAssembly;
    @Resource
    private AuthApi authApi;
    @Resource
    private SysTenantManager tenantManager;
    @Resource
    private SysTenantPackageManager tenantPackageManager;
    @Resource
    private SysMenuSceneManager sceneManager;

    @Override
    public SysMenu getMenuById(Long menuId) {
        return menuDataMapper.selectById(menuId);
    }

    @Override
    public List<Long> listMenuByRoleId(Long roleId) {
        SysRole role = roleMapper.selectRoleById(roleId);
        return menuDataMapper.selectMenuDataListByRoleId(roleId, role.isMenuCheckStrictly());
    }

    @Override
    public List<SysMenu> listMenuData(MenuListReq req, boolean admin, Long userId) {
        List<SysMenu> menuList;

        if (admin) {
            menuList = menuManager.listMenuData(req);
        } else {
            SysMenu menuData = menuAssembly.toMenu(req);
            menuData.getParams().put("userId", userId);
            menuList = menuDataMapper.selectMenuDataListByUserId(menuData);
        }
        return menuList;
    }

    @Override
    public Set<String> listMenuPermsByUserId(Long userId) {
        List<String> perms = menuDataMapper.selectMenuPermsByUserId(userId);
        Set<String> permsSet = new HashSet<>();
        for (String perm : perms) {
            if (StringUtils.isNotEmpty(perm)) {
                permsSet.addAll(Arrays.asList(perm.trim().split(",")));
            }
        }
        return permsSet;
    }

    @Override
    public List<SysMenu> listLoginUserMenu(LoginUser loginUser, Boolean allSceneMenu) {
        AuthUserTenant authUserTenant = SecurityUtils.getLoginUser().getTenant();
        String tenantId = TenantContext.get().getTenantId();
        Long sceneId = TenantCons.SYSTEM_TENANT_PACKAGE_ID;
        if (!authUserTenant.getIsSysAdmin()) {
            SysTenant tenant = tenantManager.getById(tenantId);
            if (Objects.isNull(tenant)) {
                throw new BizException(AuthErrorEnum.USER_NO_ASSIGN_ANY_ORG);
            }
            sceneId = tenant.getSceneId();
        }

        return listLoginUserMenu(loginUser, sceneId, allSceneMenu);
    }

    @Override
    public List<SysMenu> listLoginUserMenu(LoginUser loginUser, Long sceneId, Boolean allSceneMenu) {
        AuthUserTenant currentTenant = loginUser.getTenant();
        List<SysMenu> list;
        List<SysMenu> sceneMenuList = SpringUtils.getAopProxy(this).listMenu().stream()
                .filter(e -> Objects.isNull(sceneId)  || e.getSceneId().compareTo(sceneId) == 0)
                .filter(e -> MenuCons.Status.NORMAL.equals(e.getStatus()))
                .sorted(Comparator.comparing(SysMenu::getSortNum).reversed())
                .collect(Collectors.toList());

        // 因为是登录菜单, 不查询被隐藏的菜单
        if ((currentTenant.getIsSysAdmin() && loginUser.getIsTenantOwner())
                || (Boolean.TRUE.equals(allSceneMenu) && currentTenant.getIsMgtScene())) {
            // 如果是管理平台场景租户, 那就查询平台场景下的所有菜单
            // 因为管理端租户在创建租户套餐时候, 要获取场景下的所有菜单, 所以这里也要判断一些
            list = sceneMenuList;
        }  else {
            Map<Long, SysMenu> menuMap = sceneMenuList.stream().collect(Collectors.toMap(SysMenu::getId, Function.identity()));
            List<Long> userMenuIds = menuDataMapper.listMenuIdByUserId(loginUser.getUserId());
            // 获取所有父节点, 用于前端展示菜单, 否则前端菜单展示会有问题
            List<SysMenu> listParent = TreeUtils.listFlatParent(sceneMenuList, userMenuIds, node -> {});
            Map<Long, SysMenu> menuList = userMenuIds.stream()
                    .filter(menuMap::containsKey)
                    .map(menuMap::get).collect(Collectors.toMap(SysMenu::getId, Function.identity()));
            menuList.putAll(listParent.stream().collect(Collectors.toMap(SysMenu::getId, Function.identity())));
            list = new ArrayList<>(menuList.values());
        }
        return list;
    }

    @Override
    public List<SysMenu> listAllMenuByRoleIds(boolean isAdmin, List<Long> roleIds, Long sceneId) {
        List<SysMenu> menuList;
        List<SysMenu> allMenu = SpringUtils.getAopProxy(this).listMenu().stream()
                .filter(e -> MenuCons.Status.NORMAL.equals(e.getStatus()))
                .sorted(Comparator.comparing(SysMenu::getSortNum).reversed())
                .collect(Collectors.toList());
        if (isAdmin) {
            menuList = allMenu;
        } else {
            Map<Long, SysMenu> menuMap = allMenu.stream().collect(Collectors.toMap(SysMenu::getId, Function.identity()));
            List<Long> userMenuIds = roleMenuRelManager.listMenuIdByRoleIds(roleIds);
            // 获取所有父节点, 用于前端展示菜单, 否则前端菜单展示会有问题
            List<SysMenu> listParent = TreeUtils.listFlatParent(allMenu, userMenuIds, node -> {});
            menuList = userMenuIds.stream().map(menuMap::get).collect(Collectors.toList());
            menuList.addAll(listParent);
        }
        return menuList.stream().filter(Objects::nonNull).collect(Collectors.toList());
    }

    @Override
    @Cacheable(value = CacheCons.ALL_MENU_KEY, unless = "#result == null")
    public List<SysMenu> listMenu() {
        // 查询所有的权限
        return menuDataMapper.selectList(new LambdaQueryWrapper<>());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = CacheCons.ALL_MENU_KEY, allEntries = true)
    public boolean updateMenu(SysMenu req) {
        Long parentId = ObjUtil.defaultIfNull(req.getParentId(), TreeCons.ROOT_PARENT_ID);
        req.setParentId(parentId);

        if (Objects.equals(req.getId(), parentId)) {
            throw new BizException("上级不能选择自己");
        }

        // 校验父菜单
        if (!TreeCons.ROOT_PARENT_ID.equals(parentId)) {
            SysMenu parentNode = this.getMenuById(parentId);
            if (Objects.isNull(parentNode)) {
                throw new BizException("父级菜单不存在");
            }
        }

        SysMenu menuEntity = menuManager.getById(req.getId());
        if (Objects.isNull(menuEntity)) {
            return false;
        }

        List<SysMenu> copedList = menuManager.listByCopyId(menuEntity.getCopyId());
        copedList.forEach(e -> {
            e.setComponent(req.getComponent());
            e.setPerms(req.getPerms());
        });
        this.updateBatchById(copedList);


        // 更新资源动作
        LambdaQueryWrapper<SysRoleMenu> lqw = SysRoleMenu.lqw()
                .eq(SysRoleMenu::getMenuId, menuEntity.getId())
                .eq(SysRoleMenu::getSceneId, menuEntity.getSceneId());
        SysRoleMenu updated = new SysRoleMenu();
        updated.setResourceAction(req.getPerms());
        roleMenuRelManager.update(updated, lqw);

        return menuDataMapper.updateById(req) > 0;
    }

    @Override
    public List<MenuTreeResp> listMenuTree(MenuListReq req, Long userId, Long parentId) {
        LoginUser loginUser = SecurityUtils.getLoginUser();

        // 适配后台管理租户应用菜单
//        if (TenantCons.ADMIN_APP_ID.compareTo(loginUser.getCurrentAppId()) == 0) {
//            WorkspaceContentHolder.get().setAppId(Optional.ofNullable(rq.getAppId()).orElse(TenantCons.ADMIN_APP_ID));
//        }

        List<SysMenu> menus = this.listMenuData(req, loginUser.getIsTenantOwner(), userId);

        // 获取copyId为空的数据, 如果为空设置一个值并更新
        List<SysMenu> updatedList = menus.stream().filter(e -> Objects.isNull(e.getCopyId()))
                .peek(e -> e.setCopyId(YitIdHelper.nextId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(updatedList)) {
            this.updateBatchById(updatedList);
        }

        List<MenuTreeResp> flatList = menus.stream().map(menuAssembly::toMenuTreeResp).collect(Collectors.toList());
        List<MenuTreeResp> treeList = TreeUtils.build(flatList, parentId);
        if (StringUtils.isNotEmpty(req.getName())) {
            TreeUtils.filter(treeList, (e) -> e.getName().contains(req.getName()));
        }

        // 将根节点id设置为nul(适配前端展示)
        treeList.forEach(e -> e.setParentId(null));
        return treeList;
    }

    @Override
    @CacheEvict(value = CacheCons.ALL_MENU_KEY, allEntries = true)
    public boolean saveMenu(SysMenu req) {
        Long parentId = ObjUtil.defaultIfNull(req.getParentId(), TreeCons.ROOT_PARENT_ID);
        req.setParentId(parentId);
        if (!TreeCons.ROOT_PARENT_ID.equals(parentId)) {
            SysMenu parentNode = this.getMenuById(parentId);
            if (Objects.isNull(parentNode)) {
                throw new BizException("父菜单不存在");
            }
        }

        // 只有后台管理用户有权限修改
        return menuDataMapper.insert(req) > 0;
    }

    /**
     * 获取的树, 带有根节点
     */
    private List<MenuTreeResp> listMenuTreeWithRoot() {
        List<MenuTreeResp> menuTree = this.listMenuTree(new MenuListReq(), SecurityUtils.getUserId(), TreeCons.ROOT_PARENT_ID);
        MenuTreeResp rootTree = new MenuTreeResp();
        rootTree.setId(TreeCons.ROOT_PARENT_ID);
        rootTree.setChildren(menuTree);
        List<MenuTreeResp> treeList = new ArrayList<>();
        treeList.add(rootTree);
        return treeList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = CacheCons.ALL_MENU_KEY, allEntries = true)
    public boolean deleteMenuById(Long menuId) {
        SysMenu menu = menuDataMapper.selectById(menuId);
        if (menu == null) {
            throw new BizException("未找到菜单信息");
        }

        // 该节点可能是子节点但也可能是其它节点的父节点,所以需要级联删除
        this.removeChildrenBy(menuId);

        // 查询关联的角色
        Set<Long> roleIds = roleMenuRelManager.listRoleIdsByMenuId(menuId);

        // 获取关联的用户并强退用户
        Set<Long> userIds = roleUserRelManager.listUserIdByRoleIds(roleIds);
        authApi.removeAccessTokenByUserIds(new ArrayList<>(userIds));

        // 删除角色授权表
        roleMenuRelManager.deleteByMenuIds(Collections.singleton(menuId));

        return menuDataMapper.deleteById(menuId) > 0;
    }

    /**
     * 根据父id删除其关联的子节点数据
     * TODO 待优化
     */
    public void removeChildrenBy(Long parentId) {
        LambdaQueryWrapper<SysMenu> query = new LambdaQueryWrapper<>();
        // 封装查询条件parentId为主键,
        query.eq(SysMenu::getParentId, parentId);
        // 查出该主键下的所有子级
        List<SysMenu> permissionList = menuDataMapper.selectList(query);
        if (CollectionUtils.isNotEmpty(permissionList)) {
            // 如果查出的集合不为空, 则先删除所有
            menuDataMapper.delete(query);
            // 再遍历刚才查出的集合, 根据每个对象,查找其是否仍有子级
            for (SysMenu sysMenu : permissionList) {
                Long id = sysMenu.getId();

                // 删除角色授权表
                roleMenuRelManager.deleteByMenuIds(Collections.singleton(id));

                // 查出的子级数量
                long num = menuDataMapper.selectCount(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, id));
                // 如果有, 则递归
                if (num > 0) {
                    this.removeChildrenBy(id);
                }
            }
        }

    }

    @Override
    public boolean checkMenuNameUnique(String menuName) {
        LambdaQueryWrapper<SysMenu> lqw = new LambdaQueryWrapper<>();
        lqw.eq(SysMenu::getName, menuName);
        return menuDataMapper.selectList(lqw).isEmpty();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void copyMenu(MenuCopyReq req) {
        try {
            List<MenuTreeResp> menuTree = this.listMenuTreeWithRoot();

            // 拷贝树
            MenuTreeResp treeResp = TreeUtils.copyTree(menuTree, TreeCons.ROOT_PARENT_ID, req.getId(), node -> {
                if (Objects.equals(node.getId(), TreeCons.ROOT_PARENT_ID)) {
                    return node.getId();
                }
                return YitIdHelper.nextId();
            });


            // 设置一级菜单名称
            List<MenuTreeResp> children = treeResp.getChildren();
            if (CollectionUtils.isEmpty(children)) {
                return;
            }
            MenuTreeResp menu = children.get(0);
            if (req.getDeleteLevel() == 1 && CollectionUtils.isNotEmpty(menu.getChildren())) {
                menu = menu.getChildren().get(0);
                menu.setParentId(TreeCons.ROOT_PARENT_ID);
                menu.setMenuType(MenuCons.Type.LEVEL1);
            }

            menu.setName(menu.getName() + req.getSuffix());
            List<SysMenu> menuList = TreeUtils.flatTree(menu).stream()
                    .filter(e -> !Objects.equals(e.getId(), TreeCons.ROOT_PARENT_ID))
                    .map(e -> menuAssembly.toMenu(e))
                    .peek(e -> e.setSceneId(req.getSceneId()))
                    .collect(Collectors.toList());

            this.saveBatch(menuList);
        } catch (Exception e) {
            if (e instanceof NullPointerException) {
                log.error("", e);
            }
            log.error(e.getMessage());
            throw new BizException("复制失败");
        }
    }

    @Override
    public MenuPermissionTreeResp listPermissionTree(Long sceneId, boolean allSceneMenu) {
        // 判断是不是平台租户(系统内置一个租户, 用于管理)
        String tenantId = TenantContext.get().getTenantId();
        if (Objects.isNull(sceneId) && TenantCons.SYSTEM_TENANT_ID.compareTo(tenantId) == 0) {
            sceneId = MenuCons.SCENE_ID_PLATFORM;
        }

        if (Objects.isNull(sceneId)) {
            SysTenant tenant = tenantManager.getById(TenantContext.get().getTenantId());
            if (Objects.isNull(tenant)) {
                throw new BizException("租户不存在");
            }
            SysTenantPackage tenantPackage = tenantPackageManager.getPackageById(tenant.getPackageId());
            if (Objects.isNull(tenantPackage)) {
                throw new BizException("当前租户套餐不存在");
            }
            sceneId = tenantPackage.getSceneId();
        }
        Assert.notNull(sceneId, new BizException("请核对租户套餐是否已绑定场景"));
        List<SysMenu> menuList = listLoginUserMenu(SecurityUtils.getLoginUser(), sceneId, allSceneMenu);
        List<MenuTreeResp> flatList = menuList.stream().map(menuAssembly::toMenuTreeResp).collect(Collectors.toList());
        List<MenuTreeResp> menuTreeList = TreeUtils.build(flatList, TreeCons.ROOT_PARENT_ID);

        MenuPermissionTreeResp result = new MenuPermissionTreeResp();
        result.setTreeList(menuTreeList);
        result.setMenuList(menuList);
        return result;
    }

    @Override
    public List<VueRouterResp> buildVueRouters(List<SysMenu> menus) {
        Map<Long, SysMenu> menuMap = menus.stream().collect(Collectors.toMap(SysMenu::getId, Function.identity()));
        List<VueRouterResp> respList = buildVueMenus(menuMap, TreeUtils.build(menus, TreeCons.ROOT_PARENT_ID));

        // 一级菜单的子菜单全部是隐藏路由，则一级菜单不显示
        for (VueRouterResp item : respList) {
            List<VueRouterResp> notHideChildren = Optional.ofNullable(item.getChildren()).orElse(Collections.emptyList())
                    .stream()
                    .filter(obj -> !obj.isHideMenu()).collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(item.getChildren()) && CollectionUtils.isEmpty(notHideChildren)) {
                item.setHideMenu(true);
            }
        }
        return respList;
    }

    @Override
    public Set<Long> reserveTreeBtnAndLeaf(List<Long> menuIds) {
        List<SysMenu> menuList = SpringUtils.getAopProxy(this).listMenu();
        List<SysMenu> treeList = TreeUtils.build(menuList, TreeCons.ROOT_PARENT_ID);

        Map<Long, SysMenu> menuMap = TreeUtils.flatTree(treeList).stream()
                .collect(Collectors.toMap(SysMenu::getId, Function.identity()));

        return menuIds.stream().filter(menuMap::containsKey).filter(id -> {
            SysMenu menu = menuMap.get(id);
            return MenuCons.Type.BUTTON.equals(menu.getMenuType()) || BoolEnum.TRUE.getCode().equals(menu.getTreeLeaf());
        }).collect(Collectors.toSet());
    }

    /**
     * 构建前端路由所需要的菜单
     *
     * @param menus 菜单列表
     * @return 路由列表
     */
    private List<VueRouterResp> buildVueMenus(Map<Long, SysMenu> menuMap, List<SysMenu> menus) {
        List<VueRouterResp> routers = new LinkedList<>();
        for (SysMenu menu : menus) {
            VueRouterResp router = new VueRouterResp();
            router.setId(menu.getId());
            router.setHideMenu(menu.getHideMenu());
            router.setRoute(menu.getRoute());
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(menu.getComponent());
            router.setAlwaysShow(menu.getAlwaysShow());
            router.setCode(menu.getCode());
            router.setRedirect(menu.getRedirect());
            VueRouterResp.Meta meta = router.genMata(menu);

            /*
             * 只有当前菜单是隐藏菜单时候才设置激活菜单, 比如有一个用户列表菜单, 还有一个用户详情页面, 但是是隐藏菜单, 当点击用户详情
             * 按钮的时候需要跳转到详情页面, 且页面菜 用户列表 菜单依旧保持高亮, 就需要设置当前激活菜单
             */
            if (Boolean.TRUE.equals(menu.getHideMenu())) {
                SysMenu parentMenu = getFirstParentAndNoHideMenu(menu.getParentId(), menuMap);
                if (Objects.nonNull(parentMenu)) {
                    meta.setCurrentActiveMenu(parentMenu.getPath());
                }
            }

            List<SysMenu> cMenus = menu.getChildren();
            if (!cMenus.isEmpty() &&
                    (MenuCons.Type.LEVEL1.equals(menu.getMenuType()) || MenuCons.Type.SUB.equals(menu.getMenuType()))) {
                router.setChildren(buildVueMenus(menuMap, cMenus));
            }
            if (!MenuCons.Type.BUTTON.equals(menu.getMenuType())) {
                routers.add(router);
            }
        }
        return routers;
    }

    /**
     * 获取第一个非隐藏菜单的父级菜单
     */
    private SysMenu getFirstParentAndNoHideMenu(Long parentId, Map<Long, SysMenu> menuMap) {
        if (Objects.isNull(parentId) || TreeCons.ROOT_PARENT_ID.equals(parentId)) {
            return null;
        }

        Long tempParentId = parentId;
        SysMenu menu;
        while (( menu = menuMap.get(tempParentId)) != null) {
            if (menu.getHideMenu()) {
                tempParentId = menu.getParentId();
                continue;
            }
            break;
        }
        return menu;
    }

    /**
     * 获取路由名称
     *
     * @param menu 菜单信息
     * @return 路由名称
     */
    private String getRouteName(SysMenu menu) {
        String routerName = StringUtils.capitalize(menu.getPath());
        // 非外链并且是一级目录（类型为目录）
        if (!isMenuOfSub(menu)) {
            routerName = StringUtils.EMPTY;
        }
        return routerName;
    }

    /**
     * 非外链并且是子菜单那
     */
    private boolean isMenuOfSub(SysMenu menu) {
        return MenuCons.Type.SUB.equals(menu.getMenuType()) &&
                Boolean.FALSE.equals(menu.getExternal());
    }

    /**
     * 获取路由地址
     *
     * @param menu 菜单信息
     * @return 路由地址
     */
    private String getRouterPath(SysMenu menu) {
        return menu.getPath();
    }
}
